#include <errno.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

#include "quadrdefs.h"


/** Typedef for pointer to function being integrated. */
typedef double (*FnP)(double);

/** Dynamically load module modName and return a pointer to function
 *  modName() defined in that module.
 */
	static FnP 
getFn(const char *modName)
{
	void *handle = dlopen(modName, RTLD_NOW);
	if (handle == NULL) {
		fprintf(stderr, "cannot open module '%s': %s\n", modName, dlerror());
		return NULL;
		//exit(1);
	}
	void *sym = dlsym(handle, modName);
	if (sym == NULL) {
		fprintf(stderr, "cannot link %s: %s\n", modName, dlerror()); exit(1);
	}
	return sym;
}

/* To find the integration accurately split the intervals of the integration into n intervals and find out summation of it.
   And then apply trapeziodal rule on it */
double calc_sum(FnP fnPtr, double lo_bound, double hi_bound, int n_intervals){
	int k;
	double sum_result = 0;

	for (k = 1; k <= (n_intervals - 1); k++){
		sum_result = sum_result + fnPtr(lo_bound + k * (hi_bound - lo_bound) / n_intervals);
	}
	return sum_result;
}

/* Funtion for calulating integral by using trapezoidal rule */
double trapezoidal(FnP fnPtr, double lo_bound, double hi_bound, int n_intervals){

	double trapezoidal_result;  /* Store trapezoidal result */
	double sum_result;    /* Store summation result */

	/*Calculate the summation */
	sum_result = calc_sum (fnPtr, lo_bound, hi_bound, n_intervals);

	/*Compute actual trapezoidal result */
	trapezoidal_result = ((hi_bound - lo_bound) / n_intervals) * ( (fnPtr(hi_bound) + fnPtr(lo_bound))/2 + sum_result);
	return trapezoidal_result;
}



/* Pass 0/1 as 2nd argument to checkNum funtion to check if it is double or not */
int checkNum (char *strPtr, int isDouble){
	int i = 0;
	int flag = 0;

	if (strPtr[0] == '.'){
		return 1;
	}

	for (i = 0; i < strlen (strPtr); i++){
		if (strPtr[i] == '.'){
			if (flag == 0)
				flag = 1;
			else
				return 1;
		}
	}

	for (i = 0; i < strlen (strPtr); i ++){
		if (isDouble == 1){

			if (!isdigit((int)strPtr[i]) && strPtr[i] != 'e' && strPtr[i] != 'E' && strPtr[i] != '+' && strPtr[i] != '.' &&
					strPtr[i] != '-'){
				return 1;
			}
		}
		else{
			if (!isdigit((int)strPtr[i])){
				return 1;
			}

		}
		/* Check for same operator does not occur. eg. 12++1, 12-+1, etc */
		if (strPtr[i] == '+' || strPtr[i] == '.' || strPtr[i] == '-'){
			if (!isdigit ((int)strPtr[i+1] )) {
				return 1;
			}
			if ( i != 0 && strPtr[i - 1] == ' ' && strPtr[i-1] == 'e'){
				return 1;
			}
		}
		if (strPtr[i] == 'e' || strPtr[i] == 'E'){
			if (!isdigit((int)strPtr[i+1]) && strlen (strPtr) <= i + 1){
				return 1;
			}
		}
	}
	return 0;
}

Params validate_input(char *line){

	Params prm;

	char *strPtr = NULL, *pEnd = NULL;
	int i = 0, flag = 1;

	strPtr = strtok (line, " \n");

	if(strcmp(strPtr, "quit") == 0){
		prm.error = 1;
		return prm;
	}

	while (strPtr != NULL){

		if ( i == 0){
			//Check if the funtion is available in fns[]
			//if ( getFnForString (strPtr)){
			prm.fnName = (char *)malloc (sizeof (char) * strlen (strPtr));
			strcpy (prm.fnName, strPtr);
			//}
			/*else{
			  fprintf (stderr, "\nERROR: Function \"%s\" is not defined\n", strPtr);
			  flag = 1;
			  prm.error = -1;
			  return prm;
			  }*/
		}

		if ( i == 1){
			/* Pass 1 as 2nd argument to checkNum funtion if the entered value is double */
			if (checkNum(strPtr, 1) == 0){
				prm.lo_bound = strtod (strPtr, &pEnd);
			}
			else{
				fprintf (stderr, "\nERROR: Invalid LO_BOUND value : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 2){
			if (checkNum (strPtr, 1) == 0){
				prm.hi_bound = strtod (strPtr, &pEnd);
			}
			else{
				fprintf (stderr, "\nERROR: Invalid HI_BOUND value : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 3){
			/* Pass 0 as 2nd argument to checkNum funtion if the entered value is not double */
			if (checkNum (strPtr, 0) == 0){
				prm.n_intervals = atoi (strPtr);
			}
			else{
				fprintf (stderr, "\nERROR: Invalid N_INTERVALS : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i >= 4){
			fprintf (stderr, "\nERROR: Illegal input %s\n", strPtr);
			flag = 1;
			prm.error = -1;
			return prm;
		}

		i++;
		/* Read next argument from line by using strtok */
		strPtr = strtok (NULL, " \n");
	}

	i--;
	if (i != 3){
		fprintf (stderr, "\nERROR: Insufficient values provided\n");
		prm.error = -1;
		return prm;
	}

	return prm;
}


int getClientRequest (){

	Params prm;
	double result = 0;
	char  *pEnd = NULL;
	char rBuf[BUFSIZE] = "";
	char rFile[BUFSIZE] = "";
	char wFile[BUFSIZE] = "";

	int rdFD, wrFD, ack = 0;
	pid_t pid, clientPID;
	int fifo, retVal, status, exitFlg = 0;

	status = mkfifo (FIFO_NAME, 0666);
	if (status == -1) {
		if (errno == EEXIST){
			fprintf (stderr, "'%s' FIFO is already present.Same FIFO will use for communication\n", FIFO_NAME);
		}
		else{
			fprintf (stderr, "Can't create fifo for communication\n");
			exit (1);
		}	
	}

	fprintf (stdout, "Daemon started. PID: %ld\n",(long)getpid());

	fifo = open ("serv_port",O_RDWR);
	if (fifo == -1 ){
		fprintf (stdout, "Can not open fifo for I/O operations\n");
		unlink(FIFO_NAME);
		exit (1);
	}

	while (1){

		rBuf[0] = '\0';
		retVal = read (fifo, rBuf , sizeof(rBuf));
		if (retVal == -1){
			fprintf (stderr, "Can't read value from clients.\n");
			exit (1);
		}

		if (retVal == 0)
			continue;

		rBuf[retVal - 1] = '\0';
		if (strncmp (rBuf,"quit",4) == 0){
			exitFlg = 1;
			break;
		}

		//fprintf (stdout,"rBuf : %s\n",rBuf);

		if (checkNum(rBuf, 1) == 0){
			clientPID = strtod (rBuf, &pEnd);
			//printf ("QUADRD:%ld New ClientPID : %ld \n",(long)getpid(), clientPID);
		}
		else{
			fprintf (stdout,"QUADRD: Invalid input received\n");
			continue;
		}

		if ((pid = fork()) < 0){
			fprintf (stderr, "fork error\n");
			close(fifo);
			exit (1);
		} 
		else if (pid == 0){
			//fprintf (stdout, "\n 1st Child : %ld\n",(long)getpid());
			if ((pid = fork ()) < 0){
				fprintf (stderr, "fork error\n");
				close(fifo);
				exit (1);
			}
			else if (pid > 0){
				//fprintf (stdout, "\n Exiting 1st Child : %ld\n",(long)getpid());
				close(fifo);
				exit (0); // Exit Parent i.e Exit 1st child.
			}


			/* 2nd Child AREA */


			//fprintf (stdout, "\n 2nd Child : %ld : Data : %s %g %g %d\n",
			//(long)getpid(),prm.fnName, prm.lo_bound, prm.hi_bound, prm.n_intervals);

			wFile[0] = '\0';
			rFile[0] = '\0';
			sprintf (wFile,"%ld_rd",clientPID);
			sprintf (rFile,"%ld_wr",clientPID);

			status = mkfifo (rFile,0666);
			if (status == -1) {
				fprintf (stderr, "Can't create fifo for communication\n");
				ack = 0;
				if (write (fifo, (void *) &ack, sizeof(ack)) == -1){
					fprintf (stderr, "Sending ack to client failed\n");
					close (fifo);
					exit (1);
				}

				exit (1);
			}

			status = mkfifo (wFile, 0666);
			if (status == -1) {
				fprintf (stderr, "Can't create fifo for communication\n");
				ack = 0;
				if (write (fifo, (void *) &ack, sizeof(ack)) == -1){
					fprintf (stderr, "Sending ack  to client failed\n");
					close (fifo);
					exit (1);
				}

				exit (1);
			}

			/* Send ack to client to proceed and send data for communication */
			ack = 1;
			if (write (fifo, (void *) &ack, sizeof(ack)) == -1){
				fprintf (stderr, "Sending result to client failed\n");
				close (fifo);
				exit (1);
			}



			rdFD = open (rFile, O_RDONLY);
			if (rdFD == -1){
				fprintf (stderr, "Can't open Read FIFO for I/O operation\n");
				exit (1);
			}

			wrFD = open (wFile, O_WRONLY);
			if (wrFD == -1){
				fprintf (stderr, "Can't open write FIFO for I/O operation\n");
				exit (1);
			}


			while(1){

				rBuf[0] = '\0';
				retVal = read (rdFD, rBuf, BUFSIZE);
				if (retVal == -1){
					fprintf (stderr, "Can't read value from clients.\n");
					break;
				}

				if (retVal == 0)
					continue;

				if (strncmp (rBuf,"quit",4) == 0){
					break;
				}	
				prm = validate_input(rBuf);
				fprintf (stdout, "\nQUADRD:%ld Received data for computation from client(%ld): %s %g %g %d\n",
						(long)getpid(), clientPID, prm.fnName, prm.lo_bound, prm.hi_bound, prm.n_intervals);

				FnP fn = getFn(prm.fnName);
				if (fn == NULL){
					result = -1;
					if (write (wrFD, (void *) &result, sizeof(result)) == -1){
						fprintf (stderr, "Sending result to client failed\n");
						break;
					}

					break;
				}
				result = trapezoidal(fn, prm.lo_bound, prm.hi_bound, prm.n_intervals);
				//fprintf (stdout, "QUADRD: Trapezoidal result of %s function is %.6g\n",prm.fnName, result);

				if (write (wrFD, (void *) &result, sizeof(result)) == -1){
					fprintf (stderr, "Sending result to client failed\n");
					break;
				}
			}
			close(rdFD);
			close(wrFD);
			exit(0);

		}
	}

	if (exitFlg == 1){
		fprintf (stdout, "Closing 'quadrd' daemon...\n");
		close (fifo);
		unlink(FIFO_NAME);
	}

	return 0;
}

	int 
main(int argc, const char *argv[])
{
	pid_t pid;
	int retVal;

	if (argc != 2) {
		fprintf(stderr, "usage: %s DIR\n", argv[0]); exit(1);
	}

	//printf ("Dir : %s\n",argv[1]);

	if ((pid = fork()) < 0){
		fprintf (stderr, "failed to create child process\n");
		exit (1);
	}
	if (pid != 0){
		//fprintf (stdout,"Exiting Parent\n");
		exit (0); /*Exit Parent so that client can act as daemon */
	}
	else{
		if (pid == 0){

			setsid ();
			if (mkdir (argv[1], 0755) == -1){
				if (errno != EEXIST){
					fprintf (stderr,"Can't create directory\n");
					exit(1);
				}
			}
			chdir (argv[1]);
			umask (0);

			retVal = getClientRequest ();
		}
	}
	return 0;
}
